/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

package io.iec.edp.caf.multicontext.listener;

import io.iec.edp.caf.commons.runtime.CafEnvironment;
import io.iec.edp.caf.commons.runtime.msu.ServiceUnitConfigService;
import io.iec.edp.caf.multicontext.classloader.PlatformClassloader;
import io.iec.edp.caf.commons.utils.JarUtil;
import io.iec.edp.caf.multicontext.support.ServiceUnitManager;
import io.iec.edp.caf.multicontext.support.SpringFactoriesOperator;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

/**
 * <p>
 * ApplicationEnvironmentPreparedEvent事件触发的时机比ApplicationContextInitializer和一般的ApplicationListener都要早
 * 在boot的prepareEviroment时就已经触发了
 * </p><p>
 * SpringFactories的加载是在SpringApplication的的构造函数里
 * caf-config-client里创建cloud时，也会构造一个SpringApplication
 * </p><p>
 * 所以在并行启动时,在CAF的cloud(caf的config-client是通过启动一个cloud来实现的)初始化SpringFactories前触发
 * 确保platformclassloader中包含所有盘里jar的url
 * 使得cloud初始化后持有所有jar里的listenner，在后续能触发
 * </p><p>
 * 该问题发现点：
 * </p>
 * <li> 1. emc在igix启动时注册了一个ApplicationEnvironmentPreparedEvent没有触发 </li>
 */
//todo 子module里也应该触发该事件
public class PlatformApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    /*
     * The default order for this listener.
     * 确保仅在CAFBootstrapApplicationListener之前执行
     */
    // public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 6;

    // private int order = DEFAULT_ORDER;
    @Override
    public int getOrder() {
        return -1;
    }

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        //非并行启动直接返回
        if(!System.getProperty("parallel.startup", "false").equals("true"))
            return;
        String serverPath = CafEnvironment.getServerRTPath() + "/";
        OriginTrackedMapPropertySource result = getOriginTrackedMapPropertySource(event);
        //拿不到配置文件信息则返回
        if(result == null) return;
        PlatformClassloader classLoader =(PlatformClassloader) Thread.currentThread().getContextClassLoader();

        //存储底座中的su
        Map<String,String> sus = new HashMap<>();
        Map<String, URLClassLoader> appClassPath = classLoader.getAppClasspath();

        //获取所有已启用的su
        List<String> enableSu = ServiceUnitConfigService.getEnableSu();
        //todo 这里只处理底座的吗？module是否也该处理，platformclassloader里是放了所有jar的一个classloader
        for(int i=0;;i++){
            //迭代获取底座路径 没有找到直接转成list的方法
            String propertyName = String.format("parallel.base.paths[%d]", i);
            if(result.getProperty(propertyName)==null) break;
            String path = Objects.requireNonNull(result.getProperty(propertyName)).toString();

            ServiceUnitManager.getServiceUnitInfo(sus,new File(serverPath+path));

            //控制底座中的su启停
            sus.forEach((su,suPath)->{
                for(String enable:enableSu){
                    if(enable.equalsIgnoreCase(su)){
                        //将底座中的su放入缓存中
                        URL[] urls = JarUtil.getJarPathFromDirPaths(suPath);
                        if(!appClassPath.containsKey(su.toLowerCase())){
                            appClassPath.put(su.toLowerCase(), new URLClassLoader(urls));
                        }
                        classLoader.addPlatformURL(urls);
                        break;
                    }
                }
            });
        }


//        //中间件
//        String container = result.getProperty("server.container").toString();
//        if(container==null) container = "tomcat";
//        Path path = Paths.get(CafEnvironment.getStartupPath(), "middleware", "servlet", container);
//        URL[] urls = JarUtil.getJarPathFromDirPaths(path.toString());
//        classLoader.addPlatformURL(urls);
        //SpringFactories有缓存，更新时须新增对象
        //或考虑清除缓存
        //Thread.currentThread().setContextClassLoader(new PlatformClassloader(classLoader.getParent()));
        //todo 这里清理缓存后，LaunchUrlClassLoader的那个缓存也被清理了，目前没发现啥问题，未来待定？？
        SpringFactoriesOperator.clearSpringFactoriesCache();
        //todo 这句话是不是没用了
        Thread.currentThread().setContextClassLoader(classLoader);
    }

    private static OriginTrackedMapPropertySource getOriginTrackedMapPropertySource(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        MutablePropertySources sources = environment.getPropertySources();
        Iterator<PropertySource<?>> iterator = sources.iterator();
        OriginTrackedMapPropertySource result = null;

        //迭代获取application.yaml中对底座路径的配置
        //todo 后续是否从独立配置文件parallel-startup.yaml中读取
        while(iterator.hasNext()){
            Object source = iterator.next();
            if(source.getClass().getName().contains("OriginTrackedMapPropertySource")){
                result = (OriginTrackedMapPropertySource)source;
            }
        }
        return result;
    }
}
